home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / ccmd / ccmd.c < prev    next >
Encoding:
C/C++ Source or Header  |  1988-08-19  |  39.2 KB  |  1,264 lines

  1. /*
  2.  Author: Andrew Lowry
  3.  
  4.  Columbia University Center for Computing Activities, July 1986.
  5.  Copyright (C) 1986, 1987, Trustees of Columbia University in the City
  6.  of New York.  Permission is granted to any individual or institution
  7.  to use, copy, or redistribute this software so long as it is not sold
  8.  for profit, provided this copyright notice is retained.
  9. */
  10. /* ccmd.c
  11. **
  12. ** Primary module of the ccmd package.  Following routines are accessible
  13. ** to user programs:
  14. **
  15. ** THis comment is all wrong!!
  16. **
  17. **      csbini - Call this routine to prepare a new CSB for parsing
  18. **         prior to its first use.     
  19. **    cmini  - Call this routine to issue a prompt and reset the
  20. **         CSB for a new parse.
  21. **    comnd  - Call this routine to parse a single field of the
  22. **          command line.
  23. **/
  24.  
  25. #include "ccmdlib.h"        /* get standard package symbols */
  26. #include "cmfncs.h"        /* and internal symbols */
  27.  
  28. extern int (*(stdact[]))();    /* standard action table */
  29.  
  30. csb cmcsb = {            /* CSB for all parsing */
  31.     0,            /* all flags initially off */
  32.     0,            /* second flag word too. */
  33.     stdin,            /* input from console */
  34.     stdout,            /* output to console */
  35.     stderr,            /* error output */
  36.     NULL,            /* no prompt set yet */
  37.     NULL,            /* no command line buffer yet */
  38.     NULL,                /* no characters to parse */
  39.     0,            /* size of nonexistent command buffer */
  40.     0,            /* no unparsed characters */
  41.     NULL,            /* no history point */
  42.     NULL,            /* no atom buffer yet */
  43.     0,            /* size of nonexistent atom buffer */
  44.     NULL,            /* no work buffer yet */
  45.     0,            /* size of nonexistent work buffer */
  46.     stdact,            /* set up standard action table */
  47.     NULCHAR,        /* no saved break character */
  48.     -1,            /* maximum column position unknown */
  49.     0,            /* current column position */
  50.     CMxOK,            /* last parse error */
  51.     NULL,            /* no FDB giving incomplete parse */
  52.     NULL,            /* no reparse handler */
  53.     NULL,            /* no error handler */
  54.     ";",            /* comment to eol */
  55.     "!",            /* start delimited comment */
  56.     "!",            /* end delimited comment */
  57.     200,            /* max help tokens */
  58.     NULL,            /* nonblocking I/O handler */
  59.     -1,            /* wrap column */
  60.     NULL,            /* command history */
  61. };
  62.  
  63.  
  64.  
  65. /* cmbufs
  66. **
  67. ** Purpose:
  68. **   Declare buffers for use in command parsing.  This routine must
  69. **   be invoked once before any parsing may be performed.  Thereafter,
  70. **   only if one or more buffers need to be changed.  
  71. **
  72. **   Three buffers are declared.  The "command buffer" is used to
  73. **   collect user input during the parse.  The "atom buffer" is used
  74. **   to hold the text from a single parsed field.  Each time a parse
  75. **   succeeds, the characters consumed by the parse are copied into
  76. **   the atom buffer.  The "work buffer" is used internally by the
  77. **   ccmd package to hold text to be passed to the individual field
  78. **   parsers.  The user program should generally never touch the work
  79. **   buffer for any reason.
  80. **
  81. **   If any of the buffers overflows during parsing, an error is
  82. **   signaled to the caller.  To prevent atom buffer and work buffer
  83. **   overflow, they should be made as large as the command buffer.
  84. **   Generally, however, these two auxiliary buffers can be smaller,
  85. **   especially the atom buffer, unless individual fields are likely
  86. **   to be large.
  87. **
  88. ** Input arguments:
  89. **   cmdbuf - A pointer to an int buffer to be used to hold
  90. **     the command line during parsing.  Each character is stored
  91. **     with a flag byte in the left half of its int entry.
  92. **   cmdlen - The total number of characters available in the command
  93. **     line buffer.
  94. **   atombuf - A pointer to an auxiliary "atom buffer" to hold the text
  95. **     comprising individual fields during the parse.  Some parsing
  96. **     functions return string results via the atom buffer.
  97. **   atomlen - The total number of characters available in the atom buffer.
  98. **   workbuf - A pointer to an auxiliary "work buffer" used internally by
  99. **     the parsing routines.
  100. **   worklen - The total number of characters available in the work
  101. **     buffer.
  102. **
  103. ** Output arguments: None.
  104. **
  105. ** Returns: CMxOK always
  106. **/
  107.  
  108. int
  109. cmbufs(cmdbuf,cmdlen,atombuf,atomlen,workbuf,worklen)
  110. int *cmdbuf;
  111. char *atombuf,*workbuf;
  112. int cmdlen,atomlen,worklen;
  113. {
  114.   cmcsb._cmbfp = cmdbuf;    /* set up buffer attributes in CSB */
  115.   cmcsb._cmcnt = cmdlen;
  116.   cmcsb._cmabp = atombuf;
  117.   cmcsb._cmabc = atomlen;
  118.   cmcsb._cmwbp = workbuf;
  119.   cmcsb._cmwbc = worklen;
  120.   
  121.   cmcsb._cmptr = cmdbuf;    /* no parsed characters in buffer */
  122.   cmcsb._cminc = 0;        /* and no unparsed characters either */
  123.   cmcsb._cmhst = cmdbuf;    /* prevent history attempts */
  124.   return(CMxOK);
  125. }
  126.  
  127.  
  128.  
  129. /*
  130. ** cmraise, cmwake, cmecho
  131. **
  132. ** Purpose:
  133. **   Set or clear flags in the CSB.  Cmraise controls the CM_RAI flag,
  134. **   which determines whether or not characters are automatically converted
  135. **   to upper case on input.  Cmwake controls the CM_WKF flag, which controls
  136. **   whether or not a parsing "wakeup" occurs for non-action break characters
  137. **   that are encountered in the input.  Cmecho controls the CM_NEC flag,
  138. **   which determines whether or not input characters are echoed back to
  139. **   the user.
  140. **
  141. ** Input arguments:
  142. **   flag - TRUE to turn on an action, FALSE to turn it off.
  143. **
  144. ** Output arguments: None
  145. ** Returns: CMxOK always
  146. **/
  147.  
  148. int
  149. cmraise(flag)
  150. int flag;
  151. {
  152.   if (flag)
  153.     cmcsb._cmflg |= CM_RAI;    /* set the flag */
  154.   else
  155.     cmcsb._cmflg &= ~CM_RAI;    /* or clear it */
  156.   return(CMxOK);
  157. }
  158.  
  159. int
  160. cmwake(flag)
  161. int flag;
  162. {
  163.   if (flag)
  164.     cmcsb._cmflg |= CM_WKF;    /* set the flag */
  165.   else
  166.     cmcsb._cmflg &= ~CM_WKF;    /* or clear it */
  167.   return(CMxOK);
  168. }
  169.  
  170. int
  171. cmecho(flag)
  172. int flag;
  173. {
  174.   if (flag)
  175.     cmcsb._cmflg &= ~CM_NEC;    /* clear "no echo" flag */
  176.   else
  177.     cmcsb._cmflg |= CM_NEC;    /* or set it */
  178. }
  179.  
  180.  
  181.  
  182. /* cmact
  183. **
  184. ** Purpose:
  185. **   Install a user-specified action character table.  When a break
  186. **   character is encountered in the input, this table is examined,
  187. **   indexed by the character's ASCII code.  If the entry is non-NULL,
  188. **   it points to a function that will be invoked to perform an action
  189. **   on behalf of that character.
  190. **
  191. ** Input arguments:
  192. **   acttab - Pointer to the beginning of the action table, which
  193. **     should be an array of pointers to integer functions.  If
  194. **     NULL is passed, the standard action table will be installed.
  195. **
  196. ** Output arguments: None.
  197. ** Returns: CMxOK always.
  198. **/
  199.  
  200. int
  201. cmact(acttab)
  202. int (**acttab)();
  203. {
  204.   if (acttab == NULL)
  205.     cmcsb._cmact = stdact;        /* install standard table */
  206.   else
  207.     cmcsb._cmact = acttab;        /* or caller's table */
  208.   return(CMxOK);
  209. }
  210.  
  211.  
  212.  
  213. /* cmseti
  214. **
  215. ** Purpose:
  216. **   Set the input stream for command parsing.  If NULL is passed, 
  217. **   subsequent parsing will be taken from the console.  If the
  218. **   stream is associated with a terminal device, echoing and other
  219. **   output will be performed to the same terminal.  If that terminal
  220. **   is capable of cursor control, command line editing will cause
  221. **   screen updates.  Otherwise, other mechanisms will be employed
  222. **   for presenting edits to the user.  If the source is not a terminal,
  223. **   echoing and other output will be suppressed.
  224. **
  225. ** Input arguments:
  226. **   input: input file stream (normally stdin)
  227. **   output: output file stream (normally stdout)
  228. ** Output arguments: None.
  229. ** Returns: CMxOK always.
  230. **/
  231.  
  232. int
  233. cmseti(input,output,error)
  234. FILE *input,*output,*error;
  235. {
  236.   static int priorset = FALSE;    /* true only after first invocation */
  237.   if (priorset) 
  238.     cmtend();            /* break down prior source if any */
  239.   else
  240.     priorset = TRUE;        /* do that every time after first */
  241.   cmcsb._cmij = input;        /* set input stream in CSB */
  242.   cmcsb._cmoj = output;        /* set output stream in CSB */
  243.   cmcsb._cmej = error;        /* set errout stream in CSB */
  244.   cmtset();                   /* do any necessary terminal initialization */
  245.   return(CMxOK);
  246. }
  247.  
  248.  
  249.  
  250. /* cmdone
  251. **
  252. ** Purpose:
  253. **   Invoked when the program is finished with command parsing, to
  254. **   give the terminal cleanup routines a chance to clean up the
  255. **   most recent command source.
  256. **
  257. ** Input arguments: None.
  258. ** Output arguments: None.
  259. ** Returns: Nothing.
  260. **/
  261.  
  262. cmdone()
  263. {
  264.   cmtend();            /* clean up routine */
  265. }
  266.  
  267.  
  268.  
  269. /* prompt
  270. **
  271. ** Purpose:
  272. **   Issues a prompt to the user and sets a CSB in an appropriate
  273. **   state to begin parsing a new command line.  Prompt should
  274. **   be used whenever a command line is to be parsed from the beginning
  275. **   for any reason EXCEPT a reparse.  For a reparse, skip the prompt
  276. **   call and proceed immediately with calls to 'parse' to parse the
  277. **   fields of the command as required.
  278. **
  279. ** Input arguments:
  280. **   prompt - The prompt string to be issued.  The string must be preserved
  281. **     throughout the parsing of the command line, as it will be referenced
  282. **     whenever the command line must be redisplayed (after a help request,
  283. **     when the user types ^R, etc.).  In other words, do not pass
  284. **     a pointer to dynamic storage that may be deallocated before parsing
  285. **     is complete.
  286. **
  287. ** Output arguments: none
  288. **
  289. ** Returns: Standard return code.
  290. **/
  291.  
  292.  
  293. int
  294. prompt(p)
  295. char *p;
  296. {
  297.   int ret;
  298.   cmcsb._cmflg &= ~CM_CMT;    /* new line...not in a comment */
  299.   if ((cmcsb._cmbfp == NULL) ||
  300.       (cmcsb._cmabp == NULL) ||
  301.       (cmcsb._cmwbp == NULL)
  302.      )
  303.     return(CMxBUFS);        /* no buffers set up yet */
  304.  
  305.   if (cmcsb._cmcmx == -1) {    /* haven't set CONSOLE max col position? */
  306.     ret = cmseti(stdin,stdout,stderr);    /* set up all the source attributes */
  307.     if (ret != CMxOK)
  308.       return(ret);        /* propagate problems */
  309.   }
  310.   cmxbol();            /* get to beginning of line */
  311.   cmxputs(p);            /* and issue prompt */
  312.   cmcsb._cmrty = p;        /* save pointer to prompt string */
  313.   cmcsb._cmflg &= ~(CM_ESC | CM_RPT | CM_PFE | CM_DRT | CM_CFM |
  314.             CM_SWT | CM_ACT | CM_PRS);
  315.                 /* set flags in a known state */
  316.   cmcsb._cmhst = cmcsb._cmptr+cmcsb._cminc; /* save history parse point */
  317.                 /* clear buffer */
  318.   cmcsb._cmcnt += cmcsb._cminc + (cmcsb._cmptr - cmcsb._cmbfp);
  319.   cmcsb._cmptr = cmcsb._cmbfp;
  320.   cmcsb._cminc = 0;
  321.   cmcsb._cmerr = CMxOK;        /* clear last parse error */
  322.   return(CMxOK);        /* return success */
  323. }
  324.  
  325.  
  326.  
  327. /* parse
  328. **
  329. ** Purpose:
  330. **   Attempt to parse a single field of a command line, using a list
  331. **   of alternate FDB's describing the possible field contents.  Gather
  332. **   more input if required, and perform  actions as required for action
  333. **   characters, and defaulting as appropriate.
  334. **
  335. ** Input arguments:
  336. **   fdblist - A pointer to the first FDB in a list of alternative FDB's
  337. **     to be used for the parse.
  338. **
  339. ** Output arguments:
  340. **   value - A pointer to a pval item which will be filled in with the
  341. **     value resulting from a successful parse, as appropriate for the 
  342. **     type of field parsed.  (A pointer to an existing pval item must
  343. **     be passed.  No item will be allocated by the ccmd package.  Rather,
  344. **     the passed item will be filled in by ccmd.)
  345. **   usedfdb - A pointer to the FDB that succeeded in a successful parse.
  346. **
  347. ** Returns: Standard return code (CMxOK for good parse)
  348. **/
  349.  
  350. int
  351. parse(fdblist,value,usedfdb)
  352. fdb *fdblist;
  353. pval *value;
  354. fdb **usedfdb;
  355. {
  356.   int ret;            /* return codes from aux routines */
  357.   char brk;            /* break character from command input */
  358.   brktab *btab;            /* break table for current field */
  359.   fdb *f;            /* for stepping through chain */
  360.  
  361.   ret = CMxOK;            /* assume everything will be fine */
  362.   if (cmcsb._cmrty == NULL)
  363.     ret = CMxPMT;        /* can't do this until prompt issued */
  364.  
  365.   else if (fdblist == NULL)    /* Check for empty list of alternatives */
  366.     ret = CMxNFDB;
  367.   else 
  368.     for (f = fdblist; f != NULL; f = f->_cmlst)
  369.       if (badfnc(f->_cmfnc))    /* Check each FDB's function code */
  370.         ret = CMxUNKF;
  371.  
  372.   if (ret == CMxOK)        /* OK so far? */
  373.     ret = getbrk(fdblist,&btab); /* find correct break table for this field */
  374.  
  375.   if (ret == CMxOK)        /* still OK? */
  376.     do {            /* try parsing until success or error */
  377.       skipws(btab);        /* skip whitespace */
  378.       ret = checkcfm(fdblist,btab); /* stuff default string if needed */
  379.       if (ret != CMxOK)
  380.     return(ret);        /* propagate errors */
  381.       skipws(btab);        /* skip any space that got stuffed */
  382.       if (cmcsb._cminc > 0) {
  383.       int temp = *cmcsb._cmptr & 0x7f;
  384.       if (BREAK(btab,temp,cmcsb._cminc)) {
  385.           if (*cmcsb._cmptr & CC_ACT) {
  386.           cmcsb._cmflg |= CM_ACT;
  387.           ret = checkbrk(fdblist,btab,(char) *cmcsb._cmptr, 
  388.                      *cmcsb._cmptr);
  389.           if (ret == CMxGO)
  390.               ret = checkact(fdblist,btab,*cmcsb._cmptr);
  391.           }
  392.           }
  393.       }
  394.       ret = tryparse(fdblist,value,usedfdb); /* attempt a parse */
  395.       if (ret == CMxOK)
  396.     cmcsb._cmflg |= CM_PRS;    /* something parsed now */
  397.       if (ret != CMxINC)     /* keep going if more input needed */
  398.     break;            /* anything else... exit loop and handle */
  399.       cmcsb._cmifd = *usedfdb;    /* save pointer to FDB that gave CMxINC */
  400.                 /* do waiting action if needed */
  401.       ret = checkact(fdblist,btab,0);
  402.       if (ret == CMxGO)        /* if action wants wakeup... */
  403.     continue;        /*  then back to top of this loop */
  404.       while (ret == CMxOK) {    /* if action ok, loop until wakeup or error */
  405.     ret = fill(btab,&brk);    /* get more input from user */
  406.     if (ret != CMxOK)    /* problems with filling... */
  407.       break;        /*  then exit loops to handle */
  408.                 /* handle break character */
  409.     ret = checkbrk(fdblist,btab,brk,0);
  410.       }
  411.     } while (ret == CMxGO);    /* continue loop only if wakeup requested */
  412.  
  413.   /* We get here after a successful parse, or a reparse, or an error */
  414.  
  415.   if (ret == CMxRPT) {        /* reparse needed? */
  416.     cmcsb._cminc += (cmcsb._cmptr - cmcsb._cmbfp); /* all chars unparsed */
  417.     cmcsb._cmptr = cmcsb._cmbfp;
  418.     cmcsb._cmflg &= ~CM_CMT;    /* no longer inside a comment */
  419.     cmcsb._cmflg |= CM_RPT;    /* flag the condition */
  420.     cmcsb._cmflg &= ~CM_PRS;    /* back to nothing parsed */
  421.     if (cmcsb._cmrph != NULL)    /* reparse handler available? */
  422.       ret = (*cmcsb._cmrph)();    /* then call it */
  423.   }
  424.   if ((ret != CMxOK) && (cmcsb._cmerh != NULL)) { /* handlable error? */
  425.     cmcsb._cmerr = ret;        /* save the error */
  426.     ret = (*cmcsb._cmerh)(ret); /* and invoke the handler */
  427.   }
  428.   /* error without handler, or handler failed, or good parse gets here */
  429.   if (ret != CMxOK)        /* save parse error code in CSB */
  430.     cmcsb._cmerr = ret;
  431.   return(ret);
  432. }
  433.  
  434.  
  435.  
  436. /* getbrk
  437. **
  438. ** Purpose:
  439. **   Locate and return the correct break table to be used in parsing a 
  440. **   field.  If a user-supplied break table is encountered in any of the
  441. **   supplied FDB's, that is returned.  Otherwise, the default break table
  442. **   for the first FDB is returned.
  443. **
  444. ** Input arguments:
  445. **   fdblist - A pointer to the first fdb in the chain of alternates.
  446. **
  447. ** Output arguments:
  448. **   btab - A pointer to the break table to be used.
  449. **
  450. ** Returns: Standard return code (CMxOK always).
  451. **/
  452.  
  453. static int
  454. getbrk(fdblist,btab)
  455. fdb *fdblist;
  456. brktab **btab;
  457. {
  458.   *btab = cmfntb[fdblist->_cmfnc-1]->_ftbrk; /* std tbl until overruled */
  459.   while (fdblist != NULL)     /* search through given FDB's */
  460.     if (fdblist->_cmbrk != NULL) { /* user supplied table? */
  461.       *btab = fdblist->_cmbrk;    /* yup, overrule standard table */
  462.       break;            /* and stop looking */
  463.     }
  464.     else
  465.       fdblist = fdblist->_cmlst; /* otherwise move on down the list */
  466.  
  467.   return(CMxOK);        /* That's all... */
  468. }
  469.  
  470.  
  471.  
  472. /* checkcfm
  473. **
  474. ** Purpose:
  475. **   Checks to see whether the current field needs a default value
  476. **   filled in by reason that the command line has been confirmed.
  477. **   If so, the default is stuffed quietly into the command buffer.
  478. **   To qualify, the CM_CFM flag must be on in the CSB, and the command
  479. **   line buffer must contain exactly one unparsed character, that
  480. **   matching the character stored in _cmbkc of the CSB, the character
  481. **   that caused the confirm in the first place.  In addition, the
  482. **   current chain of FDB's must not contain one whose handler has
  483. **   the FN_DFX default-blocking flag turned on.  If a field meets
  484. **   these criteria but the confirming character is not considered
  485. **   a break character for this field, then the CM_CFM flag is turned
  486. **   off and no default is stuffed.  
  487. **
  488.  
  489. **   The following no longer applies...
  490. **   As a special case, if all criteria
  491. **   for defaulting are met, but the CM_PRS flag is off in the CSB,
  492. **   implying that only whitespace preceded the confirmation, the entire
  493. **   line buffer will be discarded and the prompt reissued, effectively
  494. **   restarting the parse from the top.
  495.  
  496. **
  497. ** Input arguments:
  498. **   fdblist - A pointer to the first FDB in the chain of alternates.
  499. **   btab - A pointer to the break table to be used for this field.
  500. **
  501. ** Output arguments: None
  502. ** Returns: CMxOK for all normal returns, anything else is an error.
  503. **/
  504.  
  505. static int
  506. checkcfm(fdblist,btab)
  507. fdb *fdblist;
  508. brktab *btab;
  509. {
  510.   int ret = CMxOK;        /* assume everything will be OK */
  511.   int blocked = FALSE;        /* assume not blocked */
  512.   int cc;            /* next unparsed char in buffer */
  513.   char c;
  514.   fdb *f;            /* for stepping through FDB chain */
  515.   
  516.   for (f = fdblist; f != NULL; f = f->_cmlst)
  517.     if (cmfntb[f->_cmfnc-1]->_ftflg & FT_DFX) {
  518.       blocked = TRUE;        /* blocked */
  519.       break;            /* stop looking */
  520.     }
  521.  
  522.   c = (cc = *cmcsb._cmptr) & CC_CHR; /* get first unparsed char */
  523.   if (!blocked &&        /* if we're not blocked */
  524.       (cmcsb._cmflg & CM_CFM) && /* and flag is on */
  525.       (cmcsb._cminc == 1) &&    /* and exactly one char in buffer */
  526.       (c == cmcsb._cmbkc)    /* and it's the confirming char */
  527.      ) 
  528.     if (BREAK1(btab,c)) {    /* confirming char still break? */
  529.       char *defstr = NULL;
  530.       for (f = fdblist; f != NULL; f= f->_cmlst)
  531.     if (f->_cmdef) {
  532.       defstr = f->_cmdef;
  533.       break;
  534.     }
  535.                 /* nothing yet parsed? and no default? */
  536.       if ((cmcsb._cmflg & CM_PRS) == 0 && defstr == NULL) {
  537.     cmcsb._cmcnt += cmcsb._cminc + (cmcsb._cmptr - cmcsb._cmbfp);
  538.     cmcsb._cminc = 0;    /* right, empty the buffer */
  539.     cmcsb._cmptr = cmcsb._cmbfp;
  540.     cmcsb._cmflg &= ~CM_CFM; /* buffer no longer confirmed */
  541.     cmxbol();        /* and reissue prompt on terminal */
  542.     cmxputs(cmcsb._cmrty);
  543.     return(CMxOK);        /* now go get more input */
  544.       }
  545.       cmcsb._cminc = 0;        /* remove the unparsed confirm char */
  546.       cmcsb._cmcnt++;
  547.       ret = cmsti1(SPACE,CC_HID); /* hidden separator */
  548.       if (ret == CMxOK)
  549.         ret = cmsti(defstr,CC_HID); /* stuff default, hidden */
  550.       if (ret == CMxOK)
  551.     ret = cmsti1(c,CC_NEC); /* and put back confirm char */
  552.       if (ret == CMxOK)
  553.     *(cmcsb._cmptr + cmcsb._cminc - 1) = cc; /* and restore flags */
  554.     }
  555.     else            /* confirm char no longer breaks */
  556.       cmcsb._cmflg &= ~CM_CFM;    /* so turn off confirm flag */
  557.  
  558.  
  559.   return(ret);            /* CMxOK, unless error with stuffing */
  560. }
  561.  
  562.  
  563.  
  564. /* checkact
  565. **
  566. ** Purpose:
  567. **   This routine is invoked when an attempt to parse a field has
  568. **   returned code CMxINC (incomplete parse -- more data required).
  569. **   If there is a deferred action waiting to be invoked, the break
  570. **   character that calls for the action is first checked to make
  571. **   sure it is still considered a break character in the current
  572. **   parsing context.  If not, the character is appended to the
  573. **   command buffer, and the CM_ACT flag is cleared.  Otherwise,
  574. **   the indicated action is invoked.
  575. **
  576. ** Input arguments:
  577. **   fdblist - A pointer to the first FDB on the chain of alternates.
  578. **   btab - A pointer to the break table currently in effect.
  579. **
  580. ** Output arguments: None
  581. **
  582. ** Returns: CMxGO if an action was invoked and indicated that a wakeup
  583. **   should be performed, CMxOK if no action pending or invoked action
  584. **   did not request wakeup, or other standard return code on error.
  585. **/
  586.  
  587. static int
  588. checkact(fdblist,btab,flags)
  589. fdb *fdblist;
  590. brktab *btab;
  591. int flags;
  592. {
  593.   int ret;
  594.   
  595.   if (cmcsb._cmflg & CM_ACT) {    /* action pending? */
  596.     if (BREAK(btab,cmcsb._cmbkc,cmcsb._cminc)) { /* still a break char? */
  597.       if (cmcsb._cmact[cmcsb._cmbkc]) {
  598.     ret = (*cmcsb._cmact[cmcsb._cmbkc])(fdblist,cmcsb._cmbkc,TRUE,flags);
  599.                 /* yes, invoke routine in deferred mode */
  600.       }
  601.       else ret = CMxOK;
  602.     }
  603.     else
  604.       ret = cmsti1(cmcsb._cmbkc,0); /* deposit non-break char */
  605.  
  606.     cmcsb._cmflg &= ~CM_ACT;    /* turn off action flag either way */
  607.     return(ret);        /* and return result */
  608.   }
  609.   else
  610.     return(CMxOK);        /* no action pending => no error */
  611. }
  612.  
  613.  
  614.  
  615.  
  616. /* checkbrk
  617. **
  618. ** Purpose:
  619. **   This routine is invoked after some input has been successfully
  620. **   collected for the command line.  The break character is checked
  621. **   to see whether it is an action character, and if so, the action
  622. **   is invoked.  If the return code indicates that the action should
  623. **   be deferred, the CSB is updated accordingly.
  624. **
  625. ** Input arguments:
  626. **   fdblist - A pointer to the chain of alternate FDB's for the current
  627. **     field.
  628. **   btab - A pointer to the break table currently in effect.
  629. **   brk - The character that broke the input process.
  630. **
  631. ** Output arguments: None
  632. **
  633. ** Returns: CMxGO if a wakeup should be performed, CMxOK if more
  634. **   input should be collected, or a standard error code.
  635. **/
  636.  
  637. static int
  638. checkbrk(fdblist,btab,brk, flags)
  639. fdb *fdblist;
  640. brktab *btab;
  641. char brk;
  642. int flags;
  643. {
  644.   int ret;
  645.   int (*act)();            /* pointer to action routine */
  646.   
  647.   if ((act = cmcsb._cmact[brk]) != NULL) { /* is there an action routine? */
  648.     ret = (*act)(fdblist,brk,FALSE,flags); /* invoke it non-deferred */
  649.     if (ret == CMxDFR) {    /* action wants to defer? */
  650.       cmcsb._cmflg |= CM_ACT;    /* set the flag */
  651.       cmcsb._cmbkc = brk;    /* save the action char */
  652.       ret = CMxGO;        /* and request a wakeup */
  653.     }
  654.   }
  655.   else {            /* No action... */
  656.     ret = cmsti1(brk,0);    /* deposit brk char into command buffer */
  657.     if (ret == CMxOK)
  658.       if (cmcsb._cmflg & CM_WKF) /* waking on every field? */
  659.     ret = CMxGO;        /* yup, set return code to request wakeup */
  660.   }
  661.   return(ret);            /* back to caller... */
  662. }
  663.  
  664.  
  665.  
  666. /* tryparse
  667. **
  668. ** Purpose:
  669. **   Invokes the parsing functions specified by a chain of FDB's, passing
  670. **   the current unparsed command input for them to parse.  Each function
  671. **   either fails or not, depending on whether it is able to parse the
  672. **   current input.  In the case of failure, a standard error code is
  673. **   returned.  Otherwise, either CMxOK or CMxINC is returned.  The former
  674. **   indicates that a complete parse was performed, generally indicating
  675. **   the presence of a suitable break character in addition to the field
  676. **   contents.  CMxINC indicates that the current input is not sufficient
  677. **   for a successful parse, but further input may result in a successful
  678. **   parse.
  679. **
  680. **   If all functions fail, the code returned by the first is given back
  681. **   to the caller.  Otherwise, the first CMxOK or CMxINC code returned
  682. **   by a parsing function is passed on, without invoking the rest of the
  683. **   parsing functions.  In the case of success, the CSB is adjusted so
  684. **   as to consume the newly parsed characters, and any value returned
  685. **   by the parse is passed on to the caller, as well as a pointer to the
  686. **   succeeding FDB.  In addition, on a successful parse, the parsed
  687. **   characters are copied into the atom buffer.  On anything other than
  688. **   success, the CM_PFE flag is turned off in the CSB in order to prevent
  689. **   a following noise word from expanding, in case this field got completion.
  690. **   When CMxINC is returned, a pointer to the FDB that gave an incomplete
  691. **   parse is returned to the user.
  692. **
  693. ** Input arguments:
  694. **   fdblist - A pointer to the first FDB in the list of parsing alternatives.
  695. **
  696. ** Output arguments
  697. **   value - A pointer to a pval item which will be filled in with the
  698. **     value resulting from a successful parse, as appropriate for the 
  699. **     type of field parsed.  (A pointer to an existing pval item must
  700. **     be passed.  No item will be allocated by the ccmd package.  Rather,
  701. **     the passed item will be filled in by ccmd.)
  702. **   usedfdb - A pointer to the FDB that succeeded in a successful parse.
  703. **
  704. ** Returns: standard return code.
  705. **/
  706.  
  707. int
  708. tryparse(fdblist,value,usedfdb)
  709. fdb *fdblist;
  710. pval *value;
  711. fdb **usedfdb;
  712. {
  713.   int ret;            /* return codes from parsers */
  714.   int failret = CMxOK;        /* return code of first failing fdb */
  715.   int inputlen;            /* number of chars available for parse */
  716.   int parselen;            /* number of chars successfully parsed */
  717.   ftspec *ft;            /* function handler structure */
  718.  
  719.   ret = cmprep(cmcsb._cmwbp,cmcsb._cmwbc,&inputlen); /* clean up input */
  720.   if (ret != CMxOK)
  721.     return(ret);        /* propagate errors */
  722.   while (fdblist != NULL) {    /* loop through chain of FDB's */
  723.     ft = cmfntb[fdblist->_cmfnc-1]; /* get correct handler structure */
  724.                 /* and call the parser */
  725.     while (TRUE) {        /* loop til handler is finished */
  726.       cmcsb._cmflg &= ~CM_NAC;    /* assume ok to copy good parse to atom */
  727.       ret = (*ft->_ftprs)(cmcsb._cmwbp,inputlen,fdblist,&parselen,value);
  728.       if (ret != CMxAGN)    /* exit if handler did not ask for retry */
  729.     break;
  730.       ret = cmprep(cmcsb._cmwbp,cmcsb._cmwbc,&inputlen); /* rebuild input */
  731.       if (ret != CMxOK)
  732.     return(ret);        /* propagate failure */
  733.     }
  734.     if (ret == CMxOK) {        /* successful parse? */
  735.       *usedfdb = fdblist;    /* save ptr to successful FDB */
  736.       if ((cmcsb._cmflg & CM_NAC) == 0)
  737.         ret = toatom(cmcsb._cmwbp,parselen); /* parsed text to atom bfr */
  738.                  /* adjust cnt for skipped chars */
  739.       parselen = skipadj(cmcsb._cmptr,cmcsb._cminc,parselen);
  740.       cmcsb._cmptr += parselen;    /* update buffer pointers */
  741.       cmcsb._cminc -= parselen;    /*  to account for newly parsed text */
  742.       return(CMxOK);        /* and return success */
  743.     }
  744.     else if (ret == CMxINC) {    /* incomplete parse? */
  745.       *usedfdb = fdblist;    /* give back pointer to this FDB */
  746.       cmcsb._cmflg &= ~CM_PFE;    /* completion did not succeed */
  747.       return(CMxINC);        /* pass incomplete code along */
  748.     }
  749.     else {            /* unsuccessful parse */
  750.       if (failret == CMxOK)    /* first one? */
  751.     failret = ret;        /* yup, save first failure code */
  752.       fdblist = fdblist->_cmlst; /* and move on to next FDB */
  753.     }
  754.   }
  755.   cmcsb._cmflg &= ~CM_PFE;    /* all failed... no noise words */
  756.   return(failret);        /* and return first failure code */
  757. }
  758.  
  759.  
  760.  
  761. /* fill
  762. **
  763. ** Purpose:
  764. **   Accepts characters from the command input source and loads them
  765. **   into the command buffer until a break character or action
  766. **   character is encountered or an error condition occurs.  If
  767. **   a break character terminates the fill operation, the character
  768. **   is returned to the caller via the 'brk' argument, and a return
  769. **   code of CMxOK is given.  If an action character that is not
  770. **   currently a break character is encountered, a return is made
  771. **   with return code CMxGO, indicating that a wakeup should occur,
  772. **   but the 'brk' argument is not filled in, nor is the action
  773. **   character stuffed into the command buffer.  Instead, it is
  774. **   held until the next call to fill, at which time, if it is
  775. **   still not a break character, it is simply stuffed into the
  776. **   buffer like any non-break character.  If by the next call
  777. **   to fill, the break table has changed so that the action character
  778. **   breaks, a normal break-character return will be made to the
  779. **   caller, with 'brk' containing the action character.  The
  780. **   purpose of all these contortions is to ensure that action
  781. **   characters that are turned off for a field will be reactivated
  782. **   when the user finishes typing that field.  The reactivation
  783. **   requires a wakeup, to allow the user program to proceed to
  784. **   following parse fields.
  785. **
  786. **   As a side-effect of this call, CM_PFE flag is turned off in
  787. **   the CSB, in case the current field got completion and then 
  788. **   came up with an incomplete parse anyway.
  789. **
  790. ** Input arguments:
  791. **   btab - A pointer to the break table in effect.
  792. **
  793. ** Output arguments:
  794. **   brk - The character that broke the input.
  795. **
  796. ** Returns: Standard return code.
  797. **/
  798.  
  799. static int
  800. fill(btab,brk)
  801. brktab *btab;
  802. char *brk;
  803. {
  804.   static int heldact = -1;    /* non-breaking action char from prior call */
  805.   int ret;            /* return codes */
  806.   char c;            /* input character */
  807.   int indirend = FALSE;
  808.   int cmgetc();
  809.   
  810.   cmcsb._cmflg &= ~CM_PFE;    /* no noise word expansion */
  811.   while (cmcsb._cmcnt > 0) {    /* quit when we overflow the buffer */
  812.     if (heldact != -1)
  813.       c = heldact;        /* pick up held character */
  814.     else {
  815.       ret = cmgetc(&c,cmcsb._cmij);    /* or get a new character */
  816.       if (ret != CMxOK) {
  817.     if (ret == CMxEOF && cmcsb._cmblh) { /* nonblocking and a handler? */
  818.         ret = (*cmcsb._cmblh)(ret);    /* invoke the handler */
  819.     }
  820.     if (ret == CMxEOF && cmcsb._cmflg2 & CM_IND) {
  821.         cmindend();
  822.         cmcsb._cmflg |= CM_CFM;
  823.         cmsti1('\n',CC_NEC|CC_HID);
  824.         cmcsb._cmcol = 0;
  825.         return(CMxGO);
  826.     }
  827.     else 
  828.           return(ret);        /* propagate errors */
  829.       }
  830.     }
  831.     if ((cmcsb._cminc == 0) && /* skip white space at beginning of line */
  832.     ((c == SPACE) || (c == TAB)) && /* space or tab? */
  833.     BREAK1(btab,c) &&    /* and a break character for this field? */
  834.     (cmcsb._cmact[c] == NULL) /* and not an action char? */
  835.        ) {
  836.       ret = cmsti1(c,0);    /* add char to buffer and echo */
  837.       heldact = -1;        /* if this was held, it's not anymore */
  838.       if (ret != CMxOK)
  839.     return(ret);        /* propagate error */
  840.       continue;            /* and go get more characters */
  841.     }
  842.     else if (BREAK(btab,c,cmcsb._cminc)) { /* break character? */
  843.       *brk = c;            /* yup, pass it back to caller */
  844.       heldact = -1;        /* not held anymore */
  845.       return(CMxOK);        /* and return success */
  846.     }
  847.     else if ((heldact == -1) && /* this one was not held */
  848.          (cmcsb._cmact[c] != NULL) /* and it's a non-breaking action? */
  849.          ) {
  850.       heldact = c;        /* yup, remember it */
  851.       return(CMxGO);        /* and ask for a wakeup */
  852.     }
  853.     else if (c & CM_ACT) {
  854.       heldact = c & 0xff;
  855.       return(CMxGO);
  856.     }
  857.     else {            /* not skipped ws, not break or action */
  858.       ret = cmsti1(c,0);    /* add char to buffer and echo */
  859.       heldact = -1;        /* we're not holding it */
  860.       if (ret != CMxOK)
  861.     return(ret);        /* propagate problems */
  862.     }
  863.   }
  864.  
  865.   /* Dropped out of loop -- buffer must have overflowed */
  866.   return(CMxBOVF);        
  867. }
  868.  
  869.  
  870.  
  871. /*
  872.  * this also skips comments now 
  873.  */
  874.  
  875. /* skipws
  876. **
  877. ** Purpose:
  878. **   Skip past any spaces and tabs (white space) at the current parse
  879. **   position in the command buffer, unless they are not considered
  880. **   break characters in the current parse field.  Also, all characters
  881. **   with the CC_SKP flag and eligible characters with CC_CSK flag
  882. **   are skipped.  (As a side effect, ineligible CC_CSK flags are
  883. **   cleared.)
  884. **
  885. ** Input arguments:
  886. **   btab - A pointer to the current break table.
  887. **
  888. ** Output arguments: None.
  889. ** Returns: Nothing
  890. **/
  891.  
  892. #define CBEG cmcsb._cmntb
  893. #define CST cmcsb._cmnts
  894. #define CEND cmcsb._cmnte
  895. #define NOCOMMENT 0
  896. #define DELIMITED 1
  897. #define TOEOL 2
  898.  
  899. skipws(btab)
  900. brktab *btab;
  901. {
  902.   int cc;            /* characters from input buffer */
  903.   char c;
  904.   int cskip = 0;        /* number of chars in run of CC_CSK chars */
  905.   int cbeglen, cstlen, cendlen;
  906.   static int commenttype = NOCOMMENT;
  907.  
  908.   cendlen= CEND ? strlen(CEND) : 0;
  909.   cstlen= CST ? strlen(CST) : 0;
  910.   cbeglen= CBEG ? strlen(CBEG) : 0;
  911.  
  912.   if (!(cmcsb._cmflg & CM_CMT))
  913.     commenttype = NOCOMMENT;
  914.  
  915.   while (TRUE) {        /* at most twice, if input ends with */
  916.                 /*  a run of ineligible conditional skips */
  917.     while (cmcsb._cminc-cskip > 0) { /* loop until no more unparsed data */
  918.       c = (cc = *(cmcsb._cmptr + cskip)) & CC_QCH; /* get next char */
  919.       if (cc & CC_SKP) {    /* unconditional skip char? */
  920.     cmcsb._cmptr += cskip+1; /* yup, skip conditional skips too */
  921.     cmcsb._cminc -= cskip+1;
  922.     cskip = 0;        /* the run is over */
  923.       }
  924.       else if (cc & CC_CSK)    /* conditional skip char? */
  925.     cskip++;        /* yup, count it */
  926.       else {            /* no type of skip flag */
  927.     if (cskip > 0) {
  928.       while (cskip-- > 0)    /* turn off ineligible conditional skips */
  929.         cmcsb._cmptr[cskip] &= ~CC_CSK;
  930.       continue;        /* and rescan those characters */
  931.     }
  932.     if (((c == SPACE) || (c == TAB)) && BREAK1(btab,c)) {
  933.       cmcsb._cmptr++;    /* skip nonbreaking whitespace */
  934.       cmcsb._cminc--;
  935.     }
  936.     else if (BREAK1(btab,c)) { 
  937.       int i,j;
  938.       if (!(cmcsb._cmflg & CM_CMT)) {
  939.         if (commenttype == NOCOMMENT)
  940.           if (cbeglen > 0 && !xstrncmp(cmcsb._cmptr,CBEG,cbeglen)) {
  941.         commenttype = TOEOL;
  942.         cmcsb._cmflg |= CM_CMT;
  943.         cmcsb._cmptr += cbeglen;
  944.         cmcsb._cminc -= cbeglen;
  945.           }
  946.           else if (cstlen > 0 && !xstrncmp(cmcsb._cmptr,CST,cstlen)) {
  947.         commenttype = DELIMITED;
  948.         cmcsb._cmflg |= CM_CMT;
  949.         cmcsb._cmptr += cstlen;
  950.         cmcsb._cminc -= cstlen;
  951.           }
  952.           else 
  953.         break;
  954.         }
  955.         if (cmcsb._cmflg & CM_CMT) {
  956.           if (commenttype == TOEOL) {
  957.         for (j = cmcsb._cminc,i = 0; i < j; i++) {
  958.           char c = *cmcsb._cmptr;
  959.           if (cmcsb._cmflg & CM_CFM && cmcsb._cminc == 1) {
  960.             cmcsb._cmflg &= ~CM_CMT;
  961.           }
  962.           else {
  963.             cmcsb._cmptr++;
  964.             cmcsb._cminc--;
  965.           }
  966.         }
  967.         break;
  968.           }
  969.           else if (commenttype == DELIMITED) {
  970.         for (j = cmcsb._cminc,i = 0; i < j; i++) {
  971.           char c = *cmcsb._cmptr;
  972.           if (cmcsb._cmflg & CM_CFM && cmcsb._cminc == 1) {
  973.             cmcsb._cmflg &= ~CM_CMT;
  974.             break;
  975.           }
  976.           else if (!xstrncmp(cmcsb._cmptr,CEND,cendlen) && 
  977.                cmcsb._cminc >= cendlen || cendlen == 0) {
  978.             cmcsb._cmflg &= ~CM_CMT;
  979.             cmcsb._cmptr += cendlen;
  980.             cmcsb._cminc -= cendlen;
  981.             break;
  982.           }
  983.           else {
  984.             cmcsb._cmptr++;
  985.             cmcsb._cminc--;
  986.           }
  987.         }
  988.         break;
  989.           }
  990.           if (cmcsb._cminc < 0) cmcsb._cminc = 0;
  991.           return;
  992.         }
  993.     }
  994.     else {
  995.       if (cmcsb._cminc < 0) cmcsb._cminc = 0;
  996.       return;        /* else found our break point */
  997.     }
  998.       }
  999.     }
  1000.     if (cskip > 0) {        /* finished with an ineligible run? */
  1001.       while (cskip-- > 0)    /* turn off ineligible conditional skips */
  1002.     cmcsb._cmptr[cskip] &= ~CC_CSK;
  1003.       continue;            /* and rescan it */
  1004.     }
  1005.     else
  1006.       return;            /* skipped the whole buffer */
  1007.   }
  1008. }
  1009.  
  1010. static int
  1011. xstrncmp(s1,s2,len) int *s1; char *s2; int len; {
  1012.   for (; len > 0; len--) {
  1013.     if ((*s1 & 0xff) < *s2) return (-1);
  1014.     if ((*s1 & 0xff) > *s2) return(1);
  1015.     s1++; s2++;
  1016.   }
  1017.   return(0);
  1018. }
  1019.  
  1020.  
  1021.  
  1022. /* toatom
  1023. **
  1024. ** Purpose:
  1025. **   Copy text into the atom buffer, stripping any CC_QUO flags that
  1026. **   might be encountered.
  1027. **
  1028. ** Input arguments:
  1029. **   text - A pointer to the text to be copied.
  1030. **   textlen - The number of characters to copy.
  1031. **
  1032. ** Output arguments: None.
  1033. ** Returns: Standard return code.
  1034. **/
  1035.  
  1036. static int
  1037. toatom(text,textlen)
  1038. char *text;
  1039. int textlen;
  1040. {
  1041.   int ret = CMxOK;            /* assume it will fit */
  1042.   char *abp;                /* pointer into atom buffer */
  1043.  
  1044.   if (textlen >= cmcsb._cmabc) {    /* too big? */
  1045.     ret = CMxAOVF;            /* yup, we will return this */
  1046.     textlen = cmcsb._cmabc-1;        /* and only copy this much */
  1047.   }
  1048.   abp = cmcsb._cmabp;            /* point to atom buffer */
  1049.   while (textlen-- > 0)
  1050.     *abp++ = (*text++) & CC_CHR;    /* copy the text */
  1051.   *abp = NULCHAR;            /* tie off with a null */
  1052.   return(ret);
  1053. }
  1054.  
  1055.  
  1056.  
  1057. /* skipadj
  1058. ** 
  1059. ** Purpose:
  1060. **   Given a number of characters to consume in a buffer, adjusts that
  1061. **   count to include any skipped that occur in the buffer.
  1062. **   The passed buffer is assumed to be the command buffer, composed of
  1063. **   characters with flag bytes.
  1064. **
  1065. ** Input arguments:
  1066. **   buf - A pointer to the characters to be counted.
  1067. **   bufsize - Number of characters in buffer, including skips.
  1068. **   passcnt - Number of characters to consume, not including skips.
  1069. **
  1070. ** Output arguments: None.
  1071. ** Returns: Number of characters to consume, including skips.
  1072. **/
  1073.  
  1074. static int
  1075. skipadj(buf,bufsize,passcnt)
  1076. int *buf;
  1077. int bufsize, passcnt;
  1078. {
  1079.   int adjusted= 0;        /* char count including skips */
  1080.  
  1081.   while (passcnt > 0) {        /* pass # of chars requested */
  1082.     if ((*buf & (CC_CSK | CC_SKP)) == 0) /* next char not skiped? */
  1083.       passcnt--;        /* no, indicate progress */
  1084.     bufsize--;            /* consume it in any case */
  1085.     buf++;
  1086.     adjusted ++;        /* and bump adjusted count */
  1087.   }
  1088.   return(adjusted);
  1089. }
  1090.  
  1091. extern char *malloc();
  1092. extern char *realloc();
  1093. /* realloc that does malloc if given NULL pointer */
  1094. char *cmrealloc(ptr, size)
  1095. char *ptr;
  1096. int size;
  1097. {
  1098.     return((ptr == NULL) ? malloc(size) : realloc(ptr, size));
  1099. }
  1100.  
  1101. /*
  1102.  * match a string as parse would user input.
  1103.  */
  1104. match(string, len, fdblist, value, usedfdb, parselen)
  1105. char *string;                /* input string */
  1106. int len;                /* length of input string */
  1107. fdb *fdblist;                /* list of fdb's to try parsing */
  1108. pval *value;                /* return value */
  1109. fdb **usedfdb;                /* which fdb got used */
  1110. int *parselen;                /* how much did we parse. */
  1111. {
  1112.   csb savecsb;                /* local csb value */
  1113.   char *atmbuf, *wrkbuf, *malloc();    /* local buffers */
  1114.   int *cmdbuf;
  1115.   int ret;                /* return codes from parsers */
  1116.   brktab* btab;                /* get current break tab */
  1117.   savecsb = cmcsb;            /* save current csb */
  1118.                     /* use our own buffers */
  1119.   atmbuf = malloc(len+1);
  1120.   wrkbuf = malloc(len+1);
  1121.   cmdbuf = (int *) malloc((len + 1) * sizeof(int));
  1122.  
  1123.   cmbufs(cmdbuf,len+1, atmbuf, len+1, wrkbuf, len+1);
  1124.   cmstin(string,len,CC_NEC);        /* STI in the string */
  1125.   ret = getbrk(fdblist,&btab); /* find correct break table for this field */
  1126.   skipws(btab);
  1127.   ret = tryparse(fdblist,value,usedfdb); /* try to parse it */
  1128.   if (ret != CMxOK) {
  1129.       cmsti1('\n',CC_NEC);        /* make it break...kinda kludgy */
  1130.       ret = getbrk(fdblist,&btab);    /* find correct break table for this
  1131.                        field */
  1132.       skipws(btab);
  1133.       ret = tryparse(fdblist,value,usedfdb); /* try to parse it */
  1134.   }
  1135.   *parselen = cmcsb._cmptr - cmcsb._cmbfp;
  1136.   cmcsb = savecsb;            /* restore original csb */
  1137.   free(atmbuf);
  1138.   free(wrkbuf);
  1139.   free(cmdbuf);
  1140.   return(ret);
  1141. }
  1142.  
  1143.  
  1144. /*
  1145.  * turn on/off command history
  1146.  */
  1147.  
  1148. /*
  1149.  * create a "ring" buffer for command history.  
  1150.  * maintain a NULL entry at the end of the ring.  We need to allocate one 
  1151.  * extra buffer to do this.
  1152.  * 
  1153.  * next points to the NULL entry.
  1154.  * current points to the entry to show when going thorugh the history.
  1155.  * len is the length of the entire buffer, including the hole.
  1156.  */
  1157.  
  1158. cmhst(n) {
  1159.     cmhist *h;
  1160.     int i;
  1161.     if (n > 0) {
  1162.     n++;                /* space for a "hole" */
  1163.     if (cmcsb._cmhist == NULL || cmcsb._cmhist->len == 0) {
  1164.         h=cmcsb._cmhist=(cmhist *)cmrealloc(cmcsb._cmhist,sizeof(cmhist));
  1165.         h->current = 0;
  1166.         h->enabled = TRUE;
  1167.         h->next = 0;
  1168.         h->bufs = (cmhistbuf *)malloc(n * sizeof(cmhistbuf));
  1169.         bzero(h->bufs, n * sizeof(cmhistbuf));
  1170.         h->len = n;
  1171.         return;
  1172.     }
  1173.     else h = cmcsb._cmhist;
  1174.  
  1175.     if (n < h->len) {
  1176.         int start = (h->next + h->len - (n - 1)) % h->len;
  1177.         int cnt = 0;
  1178.         cmhistbuf *b = (cmhistbuf *)malloc(n*sizeof(cmhistbuf));
  1179.         bzero(b,n*sizeof(cmhistbuf));
  1180.         for(i = start; i < h->next; i = (i + 1) % h->len)
  1181.         if (h->bufs[i].buf) 
  1182.             break;
  1183.         else
  1184.             start++;
  1185.         for (i = start; i != h->next; i++) {
  1186.         b[i-start].buf = h->bufs[i%h->len].buf;
  1187.         b[i-start].len = h->bufs[i%h->len].len;
  1188.         h->bufs[i%h->len].buf = NULL;
  1189.         cnt++;
  1190.         }
  1191.         free_histbuf(h->bufs, h->len);
  1192.         h->bufs = b;
  1193.         h->next = h->current = cnt;
  1194.         for(i = cnt; i < n; i++) {
  1195.         b[i].buf = NULL;
  1196.         b[i].len = 0;
  1197.         }
  1198.     }
  1199.     else {
  1200.         cmhistbuf *b =(cmhistbuf *)cmrealloc(h->bufs, n*sizeof(cmhistbuf));
  1201.         bzero(b,n*sizeof(cmhistbuf));
  1202.         if (h->len > 0) {
  1203.         int start = (h->next +h->len - 1) % h->len;
  1204.         int cnt = 0;
  1205.         
  1206.         for(i = start; h->bufs[i].buf != NULL;
  1207.             i = (i + h->len-1) % h->len)
  1208.             start = i;
  1209.         for(i = start; i%h->len != h->next; i++) {
  1210.             b[i-start].buf = h->bufs[i%h->len].buf;
  1211.             b[i-start].len = h->bufs[i%h->len].len;
  1212.             h->bufs[i%h->len].buf = NULL;
  1213.             cnt++;
  1214.         }
  1215.         free_histbuf(h->bufs, h->len);
  1216.         h->bufs = b;
  1217.         for(i = cnt; i < n; i++) {
  1218.             h->bufs[i].buf = NULL;
  1219.  
  1220.             h->bufs[i].len = 0;
  1221.         }
  1222.         h->next = h->current = cnt;
  1223.         }
  1224.         else {
  1225.         h->current = h->next = 0;
  1226.         h->enabled = TRUE;
  1227.         }
  1228.     }
  1229.     h->len = n;
  1230.     }
  1231.     else {
  1232.     if (h = cmcsb._cmhist) {
  1233.         if (h->bufs) free(h->bufs);
  1234.         h->bufs = NULL;
  1235.         h->len = h->current = h->next = 0;
  1236.     }
  1237.     else {
  1238.          h = cmcsb._cmhist = (cmhist *)malloc(sizeof(cmhist));
  1239.         h->bufs = NULL;
  1240.         h->len = h->current = h->next = 0;
  1241.     }
  1242.     }
  1243. }
  1244.  
  1245.  
  1246. cmhst_enable() {
  1247.     cmcsb._cmhist->enabled = TRUE;
  1248. }
  1249.  
  1250. cmhst_disable() {
  1251.     cmcsb._cmhist->enabled = FALSE;
  1252. }
  1253.  
  1254. free_histbuf(b,n)
  1255. cmhistbuf *b;
  1256. {
  1257.     int i;
  1258.     for(i = 0; i < n; i++)
  1259.     if (b[i].buf) {
  1260.         free(b[i].buf);
  1261.         b[i].buf = NULL;
  1262.     }
  1263. }
  1264.